package cn.kivensoft.xlsx.helper;

import java.io.Serializable;

public class MutiBuffer implements Serializable {

	private static final long serialVersionUID = 1L;

	protected static final int B1 = 10; // Low array maximum capacity in bits.
	protected static final int C1 = 1 << B1; // Low array maximum capacity (1024).
	protected static final int M1 = C1 - 1; // Mask.

	// Resizes up to 1024 maximum (32, 64, 128, 256, 512, 1024).
	protected byte[] _low = new byte[C1];
	// For larger capacity use multi-dimensional array.
	protected byte[][] _high = new byte[1][];

	// Holds the current length.
	protected int _length;

	// Holds current capacity.
	protected int _capacity = C1;

	final protected void increaseCapacity() {
		int highIndex = _capacity >> B1;
		if (highIndex >= _high.length) { // Resizes _high.
			byte[][] tmp = new byte[_high.length << 1][];
			System.arraycopy(_high, 0, tmp, 0, _high.length);
			_high = tmp;
		}
		_high[highIndex] = new byte[C1];
		_capacity += C1;
	}

	private final void increaseCapacity(int capacity) {
		while (capacity > _capacity) increaseCapacity();
	}

	public MutiBuffer() {
		_high[0] = _low;
	}

	public MutiBuffer(int capacity) {
		int cap = _capacity;
		while (cap < capacity) cap += C1;
		_capacity = cap;
		if (cap > C1) {
			int high_len = cap >> B1;
			_high = new byte[high_len][];
			for (int i = 1; i < high_len; ++i)
				_high[i] = new byte[C1];
		}
		_high[0] = _low;
	}

	final public int length() {
		return _length;
	}

	final public int capacity() {
		return _capacity;
	}

	final public boolean isEmpty() {
		return _length == 0;
	}

	final public MutiBuffer setLength(int newLength) {
		return setLength(newLength, (byte)0);
	}

	final public MutiBuffer setLength(int newLen, byte fillByte) {
		if (newLen <= _length) _length = newLen;
		else {
			if (newLen > _capacity) increaseCapacity(newLen);
			for (int i = _length; i < newLen; ++i)
				_high[i >> B1][i & M1] = fillByte;
			_length = newLen;
		}
		return this;
	}

	public interface onForEach {
		int call(byte[] array, int offset, int length, int result);
	}

	final public void forEach(onForEach act) {
		forEach(0, _length, 0, act);
	}

	final public void forEach(int begin, int end, onForEach act) {
		forEach(begin, end, 0, act);
	}

	final public void forEach(int begin, int end, int init, onForEach act) {
		while (begin < end) {
			byte[] array = _high[begin >> B1];
			int offset = begin & M1;
			int x = C1 - offset, y = end - begin;
			int length = x < y ? x : y;
			init = act.call(array, offset, length, init);
			if (init == -1) return;
			begin += length;
		}
	}

	final public byte[] getBytes() {
		return getBytes(0, _length);
	}

	final public byte[] getBytes(int begin, int end) {
		byte[] bytes = new byte[end - begin];
		forEach(0, _length, 0, (va, pos, len, ret) -> {
			System.arraycopy(va, pos, bytes, ret, len);
			return ret + len;
		});
		return bytes;
	}

	final public MutiBuffer append(byte value) {
		if (_length >= _capacity) increaseCapacity();
		_high[_length >> B1][_length & M1] = value;
		++_length;
		return this;
	}

	final public MutiBuffer append(byte[] value) {
		if (value == null) return this;
		return append(value, 0, value.length);
	}

	final public MutiBuffer append(byte[] value, int off, int len) {
		return set(_length, value, off, len);
	}

	final public MutiBuffer clear() {
		_length = 0;
		return this;
	}

	final public byte get(int index) {
		return index < C1 ? _low[index] : _high[index >> B1][index & M1];
	}

	/** 设置指定位置的字节内容
	 * @param index 指定位置
	 * @param src 字节内容
	 * @return
	 */
	final public MutiBuffer set(int index, byte src) {
		if (index < C1) _low[index] = src;
		else _high[index >> B1][index & M1] = src;
		return this;
	}

	/** 复制源数组的内容到指定位置
	 * @param index 指定位置 
	 * @param src 源数组
	 * @return
	 */
	final public MutiBuffer set(int index, byte[] src) {
		return src == null ? this : set(index, src, 0, src.length);
	}

	/** 复制源数组的内容到指定位置
	 * @param index 指定位置
	 * @param src 源数组
	 * @param offset 源数组起始偏移
	 * @param count 复制数量
	 * @return
	 */
	final public MutiBuffer set(int index, byte[] src, int offset, int count) {
		if (src == null || src.length == 0) return this;

		int newLen = index + count;
		if (newLen > _capacity) increaseCapacity(newLen);
		forEach(index, newLen, offset, (va, off, len, ret) -> {
			System.arraycopy(src, ret, va, off, len);
			return ret + len;
		});

		if (newLen > _length) _length = newLen;
		return this;
	}

	public final MutiBuffer append(char value) {
		int c = ((int) value) & 0x00FFFFFF;
		if (c < 0x80) {
			append((byte) c);
			return this;
		}

		int bc;
		if (c < 0x800)        bc = 0;
		else if (c < 0x10000) bc = 1;
		else                  bc = 2;

		append((byte) ((c >> ((bc + 1) * 6)) | ((0xF0 << (2 - bc)) & 0xFF)));
		for (int j = bc * 6; j >= 0; j -= 6)
			append((byte) (((c >> j) & 0x3F) | 0x80));
		return this;
	}

	public final MutiBuffer append(String text) {
		if (text == null) return this;
		return append(text, 0, text.length());
	}

	public final MutiBuffer append(String text, int off, int len) {
		if (text == null) return this;
		for (int i = off, imax = off + len; i < imax; ++i) {
			int c = ((int) text.charAt(i)) & 0x00FFFFFF;
			if (c < 0x80) {
				append((byte) c);
				continue;
			}

			int bc;
			if (c < 0x800)        bc = 0;
			else if (c < 0x10000) bc = 1;
			else                  bc = 2;

			append((byte) ((c >> ((bc + 1) * 6)) | ((0xF0 << (2 - bc)) & 0xFF)));
			for (int j = bc * 6; j >= 0; j -= 6)
				append((byte) (((c >> j) & 0x3F) | 0x80));
		}
		return this;
	}

	public final MutiBuffer appendXmlAttr(String text) {
		if (text == null) return this;
		String quot = "&quot;";
		int quotLen = quot.length();
		for (int i = 0, imax = text.length(); i < imax; ++i) {
			char c = text.charAt(i);
			if (c != '"') append(c);
			else
				for (int j = 0; j < quotLen; ++j)
					append((byte) quot.charAt(j));
		}
		return this;
	}

	public final MutiBuffer appendXmlValue(String text) {
		if (text == null) return this;
		for (int i = 0, imax = text.length(); i < imax; ++i) {
			char c = text.charAt(i);
			String mapChars;
			switch (c) {
				case '&': mapChars = "&amp;"; break;
				case '<': mapChars = "&lt;"; break;
				case '>': mapChars = "&gt;"; break;
				default: mapChars = null; break;
			}
			if (mapChars == null) append(c);
			else {
				for (int j = 0, jmax = mapChars.length(); j < jmax; ++j)
					append((byte) mapChars.charAt(j));
			}
		}
		return this;
	}

}
